//
// Created by WenYF on 2016/11/4.
//

#include "session.h"

#define USE_DEFAULT_IO

static int nDownloadCount = 0;
static int nUploadCount = 0;

typedef struct ifserver_struct {
    char *name;
    char *host;
    unsigned short port;
    ifulaid *serverUid;
} ifserver;

typedef struct ifsession_callback_struct {
    if_messageRecvCallback *mrcb;
    if_eventRecvCallback *ercb;
    if_connectSuccessedCallback *cscb;
    if_connectErrorCallback *cecb;
    if_disconnectCallback *dcb;
} ifsessionCallback;

typedef struct ifpingdata_struct {
    int id;
    unsigned long msSecond;
} pingdata;

// 这里面的你只能读取
typedef struct ifsession_struct {
    ifserver *server;
    void *parser;
    void *buffer;
    ifulaid *localUid;
    iftransport *transport;
    void *socket;
    ifsessionCallback *callback;
    char *username;
    char *token;
    int state;
    pingdata *ping;
    int msDelay;
	long lastPingTime;
	int pingInteval;
    int downloadCount;
    int uploadCount;
    BOOL authorized;
	byte cryptKey;
    // for timeout
    int counter;
	void* userdata;
} ifsession;

static void on_connect_success(ifsession *session, char* extra) {
    // TODO: here to process iq result
    logD("authorization success\n");
    // send prensence to resume

    if (session->callback->cscb) {
        session->callback->cscb(session->userdata, extra);
    }
}

static void on_disconnect(ifsession *session, char *reason) {
    if (session->callback->dcb) {
		session->callback->dcb(session->userdata, reason);
    }
}

static void on_connect_error(ifsession *session, int error, char *reason) {
    if (session->callback->cecb) {
        session->callback->cecb(session->userdata, error, reason);
    }
}

static void on_message_recv(ifsession *session, ifmessage *message) {
	//logD("recv a message: from=%s, to=%s, title=%d, body=%s, id=%d"
	//	, message->from, message->to, message->title, message->body, message->id);
    if (session->callback->mrcb) {
        session->callback->mrcb(session->userdata, message);
    }
}

static void on_event_recv(ifsession *session, ifevent *event) {
	//logD("recv a event: from=%s, to=%s, type=%d, eventType=%d, actionCode=%d, body=%s, id=%d"
	//	, event->from, event->to, event->type, event->eventType, event->actionCode, event->body, event->id);
    if (session->callback->ercb) {
        session->callback->ercb(session->userdata, event);
    }
}

static void if_session_init(ifsession *session) {
    session->authorized = FALSE;
    session->counter = CONNECT_TIMEOUT;
	session->parser = if_parser_new(session->cryptKey);
    session->socket = NULL;
    session->downloadCount = 0;
    session->uploadCount = 0;
    session->state = CONNECT_STATE_CLOSE;
    session->ping->id = 0;
    session->ping->msSecond = 0;
	session->lastPingTime = 0;
    session->msDelay = 0;
}

static void if_session_reInit(ifsession *session) {
    if (session->parser) {
        if_parser_delete(session->parser);
    }

    if_session_init(session);
}

void* if_session_new(char *serverHost, unsigned short port, char *username, char *token, byte key, int pingInteval) {
    if (!serverHost || !username) {
        logD("server host or usrename is nil.");
        return NULL;
    } else {
        int len;
        ifsession *session = (ifsession*) malloc (sizeof(ifsession));

        logD("new session:");
        logD("host = %s, port = %d, username = %s, token = %s", serverHost, port, username, token);
        // server
        session->server = (ifserver*) malloc (sizeof(ifserver));
        session->server->host = if_str_clone(serverHost);
        session->server->port = port;
        session->server->serverUid = if_create_ulaid_x(serverHost, '0');
        logD("server Uid = %s/%c", session->server->serverUid->uid, session->server->serverUid->resource);
        // io
        session->transport = NULL;
        session->buffer = if_buffer_new(NET_IO_BUF_SIZE);

        // local uid
        session->localUid = if_create_ulaid_x(username, '1');

        // username and token
		session->username = if_str_clone(username);
        if (token) {
            session->token = (char*) malloc (sizeof(char) * (if_str_len(token) + 1));
            if_str_cpy(session->token, token);
        }

        // callback
        session->callback = (ifsessionCallback*) malloc (sizeof(ifsessionCallback));
        memset(session->callback, 0, sizeof(ifsessionCallback));

        session->ping = (pingdata*) malloc (sizeof (pingdata));
        memset(session->ping, 0, sizeof(pingdata));

		// 重新init的时候，这个也不能变
		session->cryptKey = key;
		session->pingInteval = pingInteval;

        if_session_init(session);

        return (void*)session;
    }
}
void if_session_setCallback(void *s, void *userdata, if_messageRecvCallback *mrcb, if_eventRecvCallback *ercb
        , if_connectSuccessedCallback *cscb, if_connectErrorCallback *cecb
        , if_disconnectCallback *dcb) {
    if (!s) {
        return;
    } else {
        ifsession *session = (ifsession*)s;
		session->userdata = userdata;
        session->callback->mrcb = mrcb;
        session->callback->ercb = ercb;
        session->callback->cscb = cscb;
        session->callback->cecb = cecb;
        session->callback->dcb = dcb;
    }
}

static int if_open_session (ifsession *session) {
    void *buffer;
    int ret;
    ifsessionOpenPacket *packet = if_create_sessionOpen_packet(TYPE_SET);
    logD("start if_open_session");
    packet->from = if_session_getLocalUid(session);
    logD("packet->from Uid = %s/%c", packet->from->uid, packet->from->resource);
    packet->to = if_session_getServerUid(session);
    logD("packet->to Uid = %s/%c", packet->to->uid, packet->to->resource);
    packet->id = if_rand_int();
    if (session->token) {
        packet->extra = (char*) malloc (sizeof(char) * (if_str_len(session->token) + 1));
        if_str_cpy(packet->extra, session->token);
    }
	buffer = if_packet_getBuffer(packet, PACKET_TYPE_SESSION_OPEN, session->cryptKey);
    if (buffer) {
        ret = session->transport->send(session->socket, (char*)if_buffer_array(buffer)
                , if_buffer_remaining(buffer));
    } else {
        ret =  IF_NET_UNKNOWN;
    }
    if_delete_packet_x(packet, PACKET_TYPE_SESSION_OPEN);
    if_buffer_delete(buffer);

    logD("end if_open_session");
    return ret;
}

static void if_close_session(ifsession *session) {
    void *buffer;
    ifsessionClosePacket *packet = if_create_sessionClose_packet(TYPE_SET);
    packet->from = if_session_getLocalUid(session);
    packet->to = if_session_getServerUid(session);
    packet->id = if_rand_int();

    buffer = if_packet_getBuffer(packet, PACKET_TYPE_SESSION_CLOSE, session->cryptKey);
    if (buffer) {
        session->transport->send(session->socket, (const char *)if_buffer_array(buffer)
                , if_buffer_remaining(buffer));
    }
    if_delete_packet_x(packet, PACKET_TYPE_SESSION_CLOSE);

    if_buffer_delete(buffer);

}

// &if_default_transport
static int if_connect_with (ifsession *session, iftransport *trans) {
    int ret;

    if (!trans->connect) return IF_NET_NOTSUPP;
    if (!session->buffer) {
        session->buffer = if_buffer_new(NET_IO_BUF_SIZE);
        if (NULL == session->buffer) return IF_NOMEM;
    }
    logD("connect: host = %s, port = %d", session->server->host, session->server->port);
    ret = trans->connect (&session->socket, session->server->host, session->server->port);
    if (ret) return ret;
    session->transport = trans;
    ret = if_open_session (session);
    logD("end if_connect_with");
    return ret;
}

static int iks_connect_tcp (ifsession *session) {
#ifdef USE_DEFAULT_IO
    return if_connect_with (session, &if_default_transport);
#else
    // TODO
    return IF_NET_NOTSUPP;
#endif
}

static int if_local_parser(ifsession *session) {
    int len;
    BOOL close = FALSE;
    // 读取包
    while (if_parser_hasPacket(session->parser)) {
        // 得到包
        ifpacketWrapper *packetwrapper = if_parser_getPacket(session->parser);
        logD("get a packet from queue, packet type =%d", packetwrapper->type);
        if (packetwrapper) {
            switch (packetwrapper->type) {
                case PACKET_TYPE_SESSION_OPEN: {
                    // 这里得到会话打开 成功/失败 返回
                    ifsessionOpenPacket *openRet = (ifsessionOpenPacket*) packetwrapper->packet;
					if (!openRet->to) {
						openRet->to = if_create_ulaid_x(session->localUid->uid, session->localUid->resource);
					}
                    logD("type = %d", openRet->type);
                    if (openRet->type == TYPE_RESULT) {
                        // 成功
                        session->authorized = TRUE;
                        logD("extra = %s", openRet->extra);

                        if (openRet->extra) {
                            len = if_str_len(openRet->extra);
                            session->server->name = (char*) malloc (len + 1);
                            if_str_cpy_x(session->server->name, len, openRet->extra);
                        }
                        on_connect_success(session, openRet->extra);

                        session->counter = 0;
                    } else {
						char *reason = NULL;

                        // 失败
                        session->transport->close(session->socket);
                        // TODO 可以优化
                        if_session_reInit(session);
                        if (openRet->extra) {
                            len = if_str_len(openRet->extra);
                            reason = (char*) malloc (len + 1);
                            if_str_cpy_x(reason, len, openRet->extra);
                        }
                        on_connect_error(session, TYPE_AUTH_ERROR, reason);
                        close = TRUE;
                    }

                    break;
                }
                case PACKET_TYPE_SESSION_CLOSE: {
                    ifsessionClosePacket *closePacket = (ifsessionClosePacket*) packetwrapper->packet;
					if (!closePacket->to) {
						closePacket->to = if_create_ulaid_x(session->localUid->uid, session->localUid->resource);
					}
                    if (closePacket) {
                        // 关闭会话
                        session->transport->close(session->socket);
                        if (closePacket->type == TYPE_SET) {
							char *reason = NULL;
                            if (session->socket) {
                                session->transport->close(session->socket);
                            }
                            if_session_reInit(session);
                            if (closePacket->extra) {
                                len = if_str_len(closePacket->extra);
                                reason = (char*) malloc (len + 1);
                                if_str_cpy_x(reason, len, closePacket->extra);
                            }
                            on_disconnect(session, reason);
                        } else {
                            // 给服务器发送断开，软后收到其返回
                            if (session->socket) {
                                session->transport->close(session->socket);
                            }
                            if_session_reInit(session);
                            on_disconnect(session, "disconnect");
                        }

                        close = TRUE;
                    }
                    break;
                }
                case PACKET_TYPE_MESSAGE: {
                    ifmessage *message = (ifmessage*) packetwrapper->packet;
					if (!message->to) {
						message->to = if_create_ulaid_x(session->localUid->uid, session->localUid->resource);
					}
                    on_message_recv(session, message);
                    break;
                }
                case PACKET_TYPE_EVENT: {
                    ifevent *event = (ifevent*) packetwrapper->packet;
					if (!event->to) {
						event->to = if_create_ulaid_x(session->localUid->uid, session->localUid->resource);
					}
//                     // 如果是ping
//                     if (event->eventType == 0x0010) {
//                         if (event->id == session->ping->id) {
//                             // 得到当前时间
// 							long time;
// #ifdef _WIN32
// 							SYSTEMTIME systime;
// 							GetSystemTime(&systime);
// 							time = systime.wDay * 86400000 + systime.wHour * 3600000 + systime.wMinute * 60000 + systime.wSecond * 1000 + systime.wMilliseconds;
// 							session->msDelay = time - session->ping->msSecond;
// #else
//                             struct timeval tv;
//                             tv.tv_sec = 0;
//                             tv.tv_usec = 0;
//                             gettimeofday(&tv, NULL);
// 							time = tv.tv_usec / 1000 + tv.tv_sec * 1000;
//                             session->msDelay = time - session->ping->msSecond;
// #endif
// 							printf("ms delay : %d\n", session->msDelay);
// 							session->lastPingTime = time;

//                             if (session->msDelay > 60000) {
//                                 session->msDelay = 10000;
//                             }
//                             session->ping->id = 0;
//                             session->ping->msSecond = 0;
//                         } else {
//                             session->msDelay = session->pingInteval;
//                         }
//                     } else {
                        on_event_recv(session, event);
                    // }
                    break;
                }
                default:
                    break;
            }

            // 释放包
            if_delete_packet(packetwrapper);


            if (close) {
                break;
            }
        }
    }

    return IF_OK;
}

static int if_recv (ifsession *session, int timeout) {
    int len, ret;
    byte zero;
    while (1) {
        byte *buffer = if_buffer_array(session->buffer);
        // 数据读取
        len = session->transport->recv (session->socket, (char*)buffer, NET_IO_BUF_SIZE, timeout);
        if (len < 0) return IF_NET_RWERR;
        if (len == 0) {
            break;
        }
        if_buffer_setPosition(session->buffer, len);
        if_buffer_flip(session->buffer);

        // 如果只有一个字节，并且是0，那么关闭会话
        if_buffer_mark(session->buffer);
        //logW("buffer remaining = %d", if_buffer_remaining(session->buffer));
        nDownloadCount += if_buffer_remaining(session->buffer);
        session->downloadCount += if_buffer_remaining(session->buffer);
        zero = if_buffer_get(session->buffer);
        //logW("zero = %d", zero);
        if_buffer_reset(session->buffer);
        if (if_buffer_remaining(session->buffer) == 1 && zero == '\0') {
            ret = TYPE_IO_ERROR;
        } else {
            // 数据解析
            if_parser_read(session->parser, session->buffer);
            ret = if_local_parser(session);
            //logW("ret = %d", ret);
        }
        if (ret != IF_OK) return ret;
        if (!session->transport) {
            return IF_NET_NOCONN;
        }
        timeout = 0;
    }
    return IF_OK;
}

void if_session_connect(void *s, int timeout) {
    if (!s) {
        return;
    } else {
        ifsession *session = (ifsession*)s;
        int e;
        logD("if_session_connect()\n");
        if (session->state != CONNECT_STATE_CLOSE) {
            return;
        }

        logD("srever = %s\n", session->server->host);
        e = iks_connect_tcp (session);
        logW("connect ret = %d", e);
        switch (e) {
            case IF_OK:
                break;
            case IF_NET_NODNS:
                logE ("hostname lookup failed\n");
                // process error
                e = TYPE_HOST_ERROR;
                break;
            case IF_NET_NOCONN:
                logE ("connection failed\n");
                // process error
                e = TYPE_CONNECT_ERROR;
                break;
            default:
                logE ("io error\n");
                // process error
                e = TYPE_IO_ERROR;
                break;
        }

        if (e != IF_OK) {
            logE ("connect error : %d\n", e);
            on_connect_error(session, e, "connect error");
            return;
        }

        session->state = CONNECT_STATE_OPEN;
        session->counter = timeout;

        while (1) {
            // if timeout is -1, wait until had data.
            if (session->authorized) {
                e = if_recv (session, 1);
            } else {
                e = if_recv (session, 1);
				session->counter--;
            }
            //logD("counter = %d", session->counter);
            if (session->state == CONNECT_STATE_CLOSE) {
                break;
            }

            if (e == IF_OK) {
                if (session->counter == 0 && !session->authorized) {
                    logE ("auth error\n");
                    on_connect_error(session, TYPE_CONNECT_TIOMEOUT, "connect timeout");
                    break;
                } else if (session->authorized) {
// 					ifevent *ping = if_create_event(PING_EVENT_TYPE, TYPE_GET, 0);
// 					ping->id = if_rand_int();
// 					session->ping->id = ping->id;
// 					ping->from = if_session_getLocalUid(session);
// 					ping->body = if_str_clone("this is a ping");
// #ifdef _WIN32
// 					SYSTEMTIME systime;
// 					GetSystemTime(&systime);
// 					session->ping->msSecond = systime.wDay * 86400000 + systime.wHour * 3600000 + systime.wMinute * 60000 + systime.wSecond * 1000 + systime.wMilliseconds;
// #else
// 					struct timeval tv;
//                     tv.tv_sec = 0;
//                     tv.tv_usec = 0;
//                     gettimeofday(&tv, NULL);
//                     session->ping->msSecond = tv.tv_usec / 1000 + tv.tv_sec * 1000;
// #endif
// 					if (session->ping->msSecond > session->lastPingTime + session->pingInteval) {
// 						session->lastPingTime = session->ping->msSecond;
// 						if_session_sendEvent(session, ping);
// 						logD ("ping server\n");
// 					}
//                     if_delete_packet_x(ping, PACKET_TYPE_EVENT);
                }
            } else if (TYPE_IO_ERROR == e) {
                session->transport->close(session->socket);
                // session close or error crash
                logE ("io error\n");
                if_session_reInit(session);
                on_connect_error(session, TYPE_CONNECT_UNKNOW_ERROR, "io error");
                break;
            } else {
				session->transport->close(session->socket);
                logE ("unknow other error: ret = %d\n", e);

                if_session_reInit(session);
                on_connect_error(session, TYPE_CONNECT_UNKNOW_ERROR, "unknow error");
                break;
            }
        }
    }
}



void if_session_sendMessage(void *s, ifmessage *message) {
    if (!s) {
        return;
    } else {
		void *buffer = NULL;
        ifsession *session = (ifsession*) s;
        if (session->state == PACKET_TYPE_SESSION_CLOSE) {
            return;
        }
        buffer = if_packet_getBuffer(message, PACKET_TYPE_MESSAGE, session->cryptKey);
        session->transport->send(session->socket, (const char *)if_buffer_array(buffer)
                , if_buffer_remaining(buffer));
        nUploadCount += if_buffer_remaining(buffer);
        session->uploadCount = if_buffer_remaining(buffer);
        if_buffer_delete(buffer);
    }
}
void if_session_sendEvent(void *s, ifevent *event) {
    if (!s) {
        return;
    } else {
		void *buffer = NULL;
        ifsession *session = (ifsession*) s;
        if (session->state == PACKET_TYPE_SESSION_CLOSE) {
            return;
        }
        buffer = if_packet_getBuffer(event, PACKET_TYPE_EVENT, session->cryptKey);
        session->transport->send(session->socket, (const char *)if_buffer_array(buffer)
                , if_buffer_remaining(buffer));
        if_buffer_delete(buffer);
    }
}
void if_session_disconnect(void *s, BOOL immediately) {
    if (!s) {
        return;
    } else {
        ifsession *session = (ifsession*) s;
        if (session->state == PACKET_TYPE_SESSION_CLOSE) {
            return;
        }

        if (session->state == CONNECT_STATE_OPEN) {
            if (immediately) {
				//if_session_reInit(session);
                session->transport->close(session->socket);
            } else {
                session->state = CONNECT_STATE_CLOSE;
                if_close_session(session);
                //if_session_reInit(s);
            }
        }
    }
}

void if_session_delete(void *s) {
    if (!s) {
        return;
    } else {
        ifsession *session = (ifsession*) s;
        if_parser_delete(session->parser);
        if_delete_ulaid(session->localUid);
        if (session->server) {
			if (session->server->host) {
				free(session->server->host);
			}
			if (session->server->name) {
				free(session->server->name);
			}
			if (session->server->serverUid) {
				if_delete_ulaid(session->server->serverUid);
			}
            free(session->server);
        }
        if_buffer_delete(session->buffer);
        free(session->callback);
        free(session->username);
        free(session->token);

        free(session);
    }
}

int if_session_getState(void *s) {
    if (!s) {
        return CONNECT_STATE_CLOSE;
    } else {
        ifsession *session = (ifsession*) s;
        return session->state;
    }
}
int if_session_getMsDelay(void *s) {
    if (!s) {
        return -1;
    } else {
        ifsession *session = (ifsession*) s;
        return session->msDelay;
    }
}
int if_session_getDownloadCount(void *s) {
    if (!s) {
        return -1;
    } else {
        ifsession *session = (ifsession*) s;
        return session->downloadCount;
    }
}
int if_session_getUploadCount(void *s) {
    if (!s) {
        return -1;
    } else {
        ifsession *session = (ifsession*) s;
        return session->uploadCount;
    }
}
unsigned short if_session_getPort(void *s) {
    if (!s) {
        return 0;
    } else {
        ifsession *session = (ifsession*) s;
        if (session->server) {
            return session->server->port;
        } else {
            return 0;
        }
    }
}
char* if_session_getServerHost(void *s) {
    if (!s) {
        return NULL;
    } else {
        ifsession *session = (ifsession*) s;
        if (session->server) {
            return session->server->host;
        } else {
            return NULL;
        }
    }
}
char* if_session_getServerName(void *s) {
    if (!s) {
        return NULL;
    } else {
        ifsession *session = (ifsession*) s;
        if (session->server) {
            return session->server->name;
        } else {
            return NULL;
        }
    }
}
ifulaid* if_session_getServerUid(void *s) {
    if (!s) {
        return NULL;
    } else {
        ifsession *session = (ifsession*) s;
        if (session->server) {
            return if_create_ulaid_x(session->server->serverUid->uid
                    , session->server->serverUid->resource);
        } else {
            return NULL;
        }
    }
}
ifulaid* if_session_getLocalUid(void *s) {
    if (!s) {
        return NULL;
    } else {
        ifsession *session = (ifsession*) s;
        return if_create_ulaid_x(session->localUid->uid, session->localUid->resource);
    }
}

char* if_session_getUsername(void *s) {
    if (!s) {
        return NULL;
    } else {
        ifsession *session = (ifsession*) s;
        return session->username;
    }
}